﻿using System;
using System.Collections;
using System.Collections.Generic;
using UIOC.Attributes;

namespace UIOC.Observable.Collections.Generic {

    public interface IReadOnlyDictionaryData<TKey, TValue> :
        IReadOnlyDictionary<TKey, TValue> {

        TValue GetValue(TKey key);
    }

    public interface IDictionaryDataUpdated<TKey, TValue> {

        public delegate void UpdatedDelegate(TKey key, TValue oldValue, TValue newValue);

        event UpdatedDelegate Updated;

        void ClearUpdated();
    }

    public interface IDictionaryDataAdded<TKey, TValue> {

        public delegate void AddedDelegate(TKey key, TValue newValue);

        event AddedDelegate Added;

        void ClearAdded();
    }

    public interface IDictionaryDataRemoved<TKey, TValue> {

        public delegate void RemovedDelegate(TKey key, TValue oldValue);

        event RemovedDelegate Removed;

        void ClearRemoved();
    }

    public interface IDictionaryDataCleared<TKey, TValue> {

        public delegate void ClearedDelegate();

        event ClearedDelegate Cleared;

        void ClearCleared();
    }

    public interface IDictionaryDataEventDispatcher<TKey, TValue> :
        IDictionaryDataUpdated<TKey, TValue>,
        IDictionaryDataAdded<TKey, TValue>,
        IDictionaryDataRemoved<TKey, TValue>,
        IDictionaryDataCleared<TKey, TValue> {
    }

    public interface IObservableDictionaryData<TKey, TValue> :
        IObservableObject,
        IDictionaryDataEventDispatcher<TKey, TValue>,
        IReadOnlyDictionaryData<TKey, TValue> {
    }

    public interface IDictionaryDataUpdater<TKey, TValue> : IDictionaryDataUpdated<TKey, TValue> {

        bool Update(TKey key, TValue value);

        bool Update(TKey key, Func<TValue, TValue> updater);
    }

    public interface IDictionaryDataAdder<TKey, TValue> : IDictionaryDataAdded<TKey, TValue> {

        bool TryAdd(TKey key, TValue value);

        bool TryAdd(KeyValuePair<TKey, TValue> item);
    }

    public interface IDictionaryDataRemover<TKey, TValue> : IDictionaryDataRemoved<TKey, TValue> {

        bool Remove(TKey key);

        bool Remove(KeyValuePair<TKey, TValue> item);
    }

    public interface IDictionaryDataCleaner<TKey, TValue> : IDictionaryDataCleared<TKey, TValue> {

        void Clear();
    }

    public interface IWritableDictionaryData<TKey, TValue> :
        IDictionary<TKey, TValue>,
        IDictionaryDataAdder<TKey, TValue>,
        IDictionaryDataRemover<TKey, TValue>,
        IDictionaryDataUpdater<TKey, TValue>,
        IDictionaryDataCleaner<TKey, TValue>,
        IObservableDictionaryData<TKey, TValue> {

        new TValue this[TKey key] { get; set; }
    }

    public interface IDictionaryData<TKey, TValue> :
        IObject,
        IObservableDictionaryData<TKey, TValue>,
        IReadOnlyDictionaryData<TKey, TValue>,
        IWritableDictionaryData<TKey, TValue> {
    }

    [Implement(typeof(IObservableDictionaryData<,>))]
    [Implement(typeof(IDictionaryData<,>))]
    public partial class DictionaryData<TKey, TValue> : Object, IDictionaryData<TKey, TValue> {

        public event IDictionaryData<TKey, TValue>.ClearedDelegate Cleared;
        public event IDictionaryData<TKey, TValue>.AddedDelegate Added;
        public event IDictionaryData<TKey, TValue>.UpdatedDelegate Updated;
        public event IDictionaryData<TKey, TValue>.RemovedDelegate Removed;

        private IDictionary<TKey, TValue> m_Dictionary;

        protected override void Construct() {
            base.Construct();
            m_Dictionary = new Dictionary<TKey, TValue>();
        }

        protected override void Allocate() {
            base.Allocate();
        }

        protected override void Release() {
            Cleared = null;
            Added = null;
            Updated = null;
            Removed = null;
            m_Dictionary.Clear();
            base.Release();
        }

        protected override void Dispose() {
            Cleared = null;
            Added = null;
            Updated = null;
            Removed = null;
            m_Dictionary = null;
            base.Dispose();
        }

        public bool IsReadOnly => m_Dictionary.IsReadOnly;

        public TValue this[TKey key] {
            get => GetValue(key);
            set {
                if (!TryAdd(key, value)) {
                    Update(key, value);
                }
            }
        }

        public int Count => m_Dictionary.Count;

        IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
        public ICollection<TKey> Keys => m_Dictionary.Keys;

        IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
        public ICollection<TValue> Values => m_Dictionary.Values;

        public bool ContainsKey(TKey key) => m_Dictionary.ContainsKey(key);

        public bool Contains(KeyValuePair<TKey, TValue> item) => m_Dictionary.Contains(item);

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => m_Dictionary.GetEnumerator();

        public bool TryGetValue(TKey key, out TValue value) => m_Dictionary.TryGetValue(key, out value);

        public TValue GetValue(TKey key) => m_Dictionary[key];

        public bool TryAdd(KeyValuePair<TKey, TValue> item) => TryAdd(item.Key, item.Value);

        public bool TryAdd(TKey key, TValue value) {
            if (ContainsKey(key)) {
                return false;
            } else {
                DoAdd(key, value);
                return true;
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);

        public void Add(TKey key, TValue value) {
            DoAdd(key, value);
        }

        private void DoAdd(TKey key, TValue value) {
            m_Dictionary.Add(key, value);
            NotifyAdded(key, value);
        }

        public bool Remove(KeyValuePair<TKey, TValue> item) {
            if (m_Dictionary.Remove(item)) {
                NotifyRemoved(item.Key, item.Value);
                return true;
            }
            return false;
        }

        public bool Remove(TKey key) {
            if (TryGetValue(key, out var oldValue)) {
                m_Dictionary.Remove(key);
                NotifyRemoved(key, oldValue);
                return true;
            }
            return false;
        }

        public bool Update(TKey key, TValue value) {
            if (TryGetValue(key, out var oldValue)) {
                m_Dictionary[key] = value;
                NotifyUpdated(key, oldValue, value);
                return true;
            }
            return false;
        }

        public bool Update(TKey key, Func<TValue, TValue> updater) {
            UnityEngine.Assertions.Assert.IsNotNull(updater);

            if (TryGetValue(key, out var oldValue)) {
                var value = m_Dictionary[key] = updater.Invoke(oldValue);
                NotifyUpdated(key, oldValue, value);
                return true;
            }
            return false;
        }

        public void Clear() {
            m_Dictionary.Clear();
            NotifyCleared();
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
            m_Dictionary.CopyTo(array, arrayIndex);
        }

        public void ClearAdded() {
            Added = null;
        }

        public void ClearCleared() {
            Cleared = null;
        }

        public void ClearRemoved() {
            Removed = null;
        }

        public void ClearUpdated() {
            Updated = null;
        }

        private void NotifyAdded(TKey key, TValue value) {
            Added?.Invoke(key, value);
            NotifyChanged();
        }

        private void NotifyCleared() {
            Cleared?.Invoke();
            NotifyChanged();
        }

        private void NotifyRemoved(TKey key, TValue oldValue) {
            Removed.Invoke(key, oldValue);
            NotifyChanged();
        }

        private void NotifyUpdated(TKey key, TValue oldValue, TValue value) {
            Updated.Invoke(key, oldValue, value);
            NotifyChanged();
        }
    }
}
